//---------------------------------------------------------------------------------
// Copyright (c) 2008 VoiceLynx Project
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not be
//        misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//        distribution.
//---------------------------------------------------------------------------------

#include "VLPacketTranslator.h"

#include "VLPacketLogic.h"


VLPacketTranslator::VLPacketTranslator(VLPacketLogic &packetLogic)
: mLogic(packetLogic)
{

}

VLPacketTranslator::~VLPacketTranslator()
{
  for (VLPacketHandlerMap::iterator iter = mPacketHandlers.begin();
    iter != mPacketHandlers.end(); ++iter)
  {
    delete iter->second;
  }

  for (VLDisconnectRequestQueue::iterator iter = mDisconnectRequests.begin();
    iter != mDisconnectRequests.end(); ++iter)
  {
    delete *iter;
  }
}

VLRawOutgoingPacketPtr VLPacketTranslator::takeOutgoingData()
{
  VLRawOutgoingPacketPtr returnVal;

  if (mOutgoing.empty() == false)
  {
    returnVal.reset(mOutgoing.front());
    mOutgoing.pop_front();
  }

  return returnVal;
}

void VLPacketTranslator::giveIncomingData(VLRawIncomingPacketPtr rawPacket)
{
  // Convert where it came from to be a user
  VLPeer *user = getUserFromHostId(rawPacket->fromHostId);

  //Convert the incoming bytes to a datastream
  InDataStream ds(rawPacket->data);

  // Peek at the first int - it's the packet type
  VLEPacketType packetType = VL_PT_UNKNOWN;
  ds.readData(VL_PACKET_TYPE_DS_NAME, (int*) &packetType);
  ds.reset();

  VLPacketHandlerMap::iterator packetHandlerIter = mPacketHandlers.find(packetType);

  if (packetHandlerIter != mPacketHandlers.end())
  {
    packetHandlerIter->second->handlePacket(user, ds);
  }
  else
  {
    VL_TRACE("Unrecognized packet type: " << packetType);
    //VL_ASSERT_ALWAYS();
  }
}

VLDisconnectRequestPtr VLPacketTranslator::takeDisconnectRequest()
{

  VLDisconnectRequestPtr disconnectRequest(0);

  if (mDisconnectRequests.empty() == false)
  {
    disconnectRequest.reset(mDisconnectRequests.front());
    mDisconnectRequests.pop_front();
  }

  return disconnectRequest;
}

void VLPacketTranslator::onConnect(VLHostId hostId)
{
  if (mHostIdToPeerMap.find(hostId) == mHostIdToPeerMap.end())
  {
    // The ID is unique
    VLPeerPtr user = mLogic.createPeer();
    mHostIdToPeerMap[hostId] = user.get();
    mPeerToHostIdMap[user.get()] = hostId;
    user.release();

    mLogic.onConnect(mHostIdToPeerMap[hostId]);
  }
  else
  {
    // The ID is not unique - assert
    VL_ASSERT_ALWAYS();
  }
}

void VLPacketTranslator::onDisconnect(VLHostId hostId)
{
  VLHostIdPeerMap::iterator iter = mHostIdToPeerMap.find(hostId);

  if (iter == mHostIdToPeerMap.end())
  {
    // The ID is not found - we assert because we shouldn't call onDisconnect using
    // an id that is not connected
    VL_ASSERT_ALWAYS();
  }
  else
  {
    mLogic.onDisconnect(iter->second);

    VLPeer *user = iter->second;

    mHostIdToPeerMap.erase(iter);
    
    VLPeerHostIdMap::iterator userMapIter = mPeerToHostIdMap.find(user);
    if (userMapIter != mPeerToHostIdMap.end())
    {
      mPeerToHostIdMap.erase(userMapIter);
    }

    delete user;
  }
}

VLPeer *VLPacketTranslator::getUserFromHostId(VLHostId hostId)
{
  VLPeer *returnValue = 0;

  VLHostIdPeerMap::iterator iter = mHostIdToPeerMap.find(hostId);

  if (iter != mHostIdToPeerMap.end())
  {
    returnValue = iter->second;
  }

  return returnValue;
}

VLHostId VLPacketTranslator::getHostIdFromUser(VLPeer *user)
{
  VLHostId returnValue = 0;

  VLPeerHostIdMap::iterator iter = mPeerToHostIdMap.find(user);

  if (iter != mPeerToHostIdMap.end())
  {
    returnValue = iter->second;
  }

  return returnValue;
}

void VLPacketTranslator::sendPacket(VLPeer *user, VLBasePacket &packet)
{
  OutDataStream ds;
  packet.serialize(ds);

  VLRawOutgoingPacketPtr p(new VLRawOutgoingPacket());
  p->destHostId = getHostIdFromUser(user);
  p->data = ds.takeData();
  mOutgoing.push_back(p.release());
}

void VLPacketTranslator::sendPacket(std::vector<VLPeer *> users, VLBasePacket &packet)
{
  OutDataStream ds;
  packet.serialize(ds);

  VLByteArrayPtr data = ds.takeData();

  for (std::vector<VLPeer *>::iterator iter = users.begin();
    iter != users.end(); ++iter)
  {
    VLByteArrayPtr dataCopy(new VLByteArray(data->size()));
    memcpy(&(*dataCopy.get())[0], &(*data.get())[0], data->size());

    VLRawOutgoingPacketPtr p(new VLRawOutgoingPacket());
    p->destHostId = getHostIdFromUser(*iter);
    p->data = dataCopy;
    mOutgoing.push_back(p.release());
  }
}

void VLPacketTranslator::broadcastPacket(VLBasePacket &packet, VLPeer *exceptTo)
{
  OutDataStream ds;
  packet.serialize(ds);

  VLByteArrayPtr data = ds.takeData();

  for (VLPeerHostIdMap::iterator iter = mPeerToHostIdMap.begin();
    iter != mPeerToHostIdMap.end(); ++iter)
  {
    if (exceptTo != iter->first)
    {
      VLByteArrayPtr dataCopy(new VLByteArray(data->size()));
      memcpy(&(*dataCopy.get())[0], &(*data.get())[0], data->size());

      VLRawOutgoingPacketPtr p(new VLRawOutgoingPacket());
      p->destHostId = iter->second;
      p->data = dataCopy;
      mOutgoing.push_back(p.release());
    }
  }
}

void VLPacketTranslator::disconnectFromPeer( VLPeer *peer, VLEDisconnectReason reason )
{
  VLDisconnectRequestPtr request(new VLDisconnectRequest());
  request->host = getHostIdFromUser(peer);
  request->reason = reason;
  mDisconnectRequests.push_back(request.release());
}